//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.security.oauth2.server.authorization.web;

import java.io.IOException;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse.Builder;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.authentication.*;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 * 覆盖
 * 增加password模式 chenyc
 * 密码模式 http://localhost:7000/oauth2/token?grant_type=password&username=vybe8882&password=0c85ff2f8718ab9b151e105248b893b9&scope=all
 * @since 0.0.3
 */
public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
    public static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token";
    private final AuthenticationManager authenticationManager;
    private final OAuth2AuthorizationService authorizationService;
    private final RequestMatcher tokenEndpointMatcher;
    private final Converter<HttpServletRequest, Authentication> authorizationGrantAuthenticationConverter;
    private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter;
    private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter;

    OAuth2PasswordAuthenticationProvider oAuth2PasswordAuthenticationProvider;//增加password模式 chenyc

    public OAuth2TokenEndpointFilter(AuthenticationManager authenticationManager, OAuth2AuthorizationService authorizationService) {
        this(authenticationManager, authorizationService, "/oauth2/token");
    }

    public OAuth2TokenEndpointFilter(AuthenticationManager authenticationManager, OAuth2AuthorizationService authorizationService, String tokenEndpointUri) {
        this.accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
        this.errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter();
        Assert.notNull(authenticationManager, "authenticationManager cannot be null");
        Assert.notNull(authorizationService, "authorizationService cannot be null");
        Assert.hasText(tokenEndpointUri, "tokenEndpointUri cannot be empty");
        this.authenticationManager = authenticationManager;
        this.authorizationService = authorizationService;
        this.tokenEndpointMatcher = new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name());
        Map<AuthorizationGrantType, Converter<HttpServletRequest, Authentication>> converters = new HashMap();
        converters.put(AuthorizationGrantType.AUTHORIZATION_CODE, new OAuth2TokenEndpointFilter.AuthorizationCodeAuthenticationConverter());
        converters.put(AuthorizationGrantType.REFRESH_TOKEN, new OAuth2TokenEndpointFilter.RefreshTokenAuthenticationConverter());
        converters.put(AuthorizationGrantType.CLIENT_CREDENTIALS, new OAuth2TokenEndpointFilter.ClientCredentialsAuthenticationConverter());
        converters.put(AuthorizationGrantType.PASSWORD,new PasswordAuthenticationConverter());//add by chenyc
        this.authorizationGrantAuthenticationConverter = new DelegatingAuthorizationGrantAuthenticationConverter(converters);
        this.oAuth2PasswordAuthenticationProvider=new OAuth2PasswordAuthenticationProvider(authorizationService);
    }

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (!this.tokenEndpointMatcher.matches(request)) {
            filterChain.doFilter(request, response);
        } else {
            try {
                String[] grantTypes = request.getParameterValues("grant_type");
                if (grantTypes == null || grantTypes.length != 1) {
                    throwError("invalid_request", "grant_type");
                }

                Authentication authorizationGrantAuthentication = (Authentication)this.authorizationGrantAuthenticationConverter.convert(request);
                if (authorizationGrantAuthentication == null) {
                    throwError("unsupported_grant_type", "grant_type");
                }
                // 开始 增加password模式 chenyc
                if(authorizationGrantAuthentication instanceof OAuth2PasswordAuthenticationToken){
                    OAuth2PasswordAuthenticationToken oAuth2PasswordAuthenticationToken=(OAuth2PasswordAuthenticationToken)authorizationGrantAuthentication;
                    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) this.authenticationManager.authenticate(oAuth2PasswordAuthenticationToken.getUsernamePasswordAuthenticationToken());
                    oAuth2PasswordAuthenticationToken.setUsernamePasswordAuthenticationToken(usernamePasswordAuthenticationToken);
                    Authentication authentication=this.oAuth2PasswordAuthenticationProvider.authenticate(oAuth2PasswordAuthenticationToken);
                    OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;
                    this.sendAccessTokenResponse(response, accessTokenAuthentication.getAccessToken(), accessTokenAuthentication.getRefreshToken());
                }else{
                    Authentication authentication = this.authenticationManager.authenticate(authorizationGrantAuthentication);
                    OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;
                    this.sendAccessTokenResponse(response, accessTokenAuthentication.getAccessToken(), accessTokenAuthentication.getRefreshToken());
                }
                // end  增加password模式 chenyc
            } catch (OAuth2AuthenticationException var7) {
                SecurityContextHolder.clearContext();
                this.sendErrorResponse(response, var7.getError());
            }

        }
    }

    private void sendAccessTokenResponse(HttpServletResponse response, OAuth2AccessToken accessToken, OAuth2RefreshToken refreshToken) throws IOException {
        Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue()).tokenType(accessToken.getTokenType()).scopes(accessToken.getScopes());
        if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
            builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
        }

        if (refreshToken != null) {
            builder.refreshToken(refreshToken.getTokenValue());
        }

        OAuth2AccessTokenResponse accessTokenResponse = builder.build();
        ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
        this.accessTokenHttpResponseConverter.write(accessTokenResponse, (MediaType)null, httpResponse);
    }

    private void sendErrorResponse(HttpServletResponse response, OAuth2Error error) throws IOException {
        ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
        httpResponse.setStatusCode(HttpStatus.BAD_REQUEST);
        this.errorHttpResponseConverter.write(error, (MediaType)null, httpResponse);
    }

    private static void throwError(String errorCode, String parameterName) {
        OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, "https://tools.ietf.org/html/rfc6749#section-5.2");
        throw new OAuth2AuthenticationException(error);
    }

    private static class ClientCredentialsAuthenticationConverter implements Converter<HttpServletRequest, Authentication> {
        private ClientCredentialsAuthenticationConverter() {
        }

        public Authentication convert(HttpServletRequest request) {
            String grantType = request.getParameter("grant_type");
            if (!AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(grantType)) {
                return null;
            } else {
                Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
                MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
                String scope = (String)parameters.getFirst("scope");
                if (StringUtils.hasText(scope) && ((List)parameters.get("scope")).size() != 1) {
                    OAuth2TokenEndpointFilter.throwError("invalid_request", "scope");
                }

                if (StringUtils.hasText(scope)) {
                    Set<String> requestedScopes = new HashSet(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
                    return new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, requestedScopes);
                } else {
                    return new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal);
                }
            }
        }
    }

    private static class RefreshTokenAuthenticationConverter implements Converter<HttpServletRequest, Authentication> {
        private RefreshTokenAuthenticationConverter() {
        }

        public Authentication convert(HttpServletRequest request) {
            String grantType = request.getParameter("grant_type");
            if (!AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(grantType)) {
                return null;
            } else {
                Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
                MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
                String refreshToken = (String)parameters.getFirst("refresh_token");
                if (!StringUtils.hasText(refreshToken) || ((List)parameters.get("refresh_token")).size() != 1) {
                    OAuth2TokenEndpointFilter.throwError("invalid_request", "refresh_token");
                }

                String scope = (String)parameters.getFirst("scope");
                if (StringUtils.hasText(scope) && ((List)parameters.get("scope")).size() != 1) {
                    OAuth2TokenEndpointFilter.throwError("invalid_request", "scope");
                }

                if (StringUtils.hasText(scope)) {
                    Set<String> requestedScopes = new HashSet(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
                    return new OAuth2RefreshTokenAuthenticationToken(refreshToken, clientPrincipal, requestedScopes);
                } else {
                    return new OAuth2RefreshTokenAuthenticationToken(refreshToken, clientPrincipal);
                }
            }
        }
    }

    private static class AuthorizationCodeAuthenticationConverter implements Converter<HttpServletRequest, Authentication> {
        private AuthorizationCodeAuthenticationConverter() {
        }

        public Authentication convert(HttpServletRequest request) {
            String grantType = request.getParameter("grant_type");
            if (!AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(grantType)) {
                return null;
            } else {
                Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
                MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
                String code = (String)parameters.getFirst("code");
                if (!StringUtils.hasText(code) || ((List)parameters.get("code")).size() != 1) {
                    OAuth2TokenEndpointFilter.throwError("invalid_request", "code");
                }

                String redirectUri = (String)parameters.getFirst("redirect_uri");
                if (StringUtils.hasText(redirectUri) && ((List)parameters.get("redirect_uri")).size() != 1) {
                    OAuth2TokenEndpointFilter.throwError("invalid_request", "redirect_uri");
                }

                Map<String, Object> additionalParameters = (Map)parameters.entrySet().stream().filter((e) -> {
                    return !((String)e.getKey()).equals("grant_type") && !((String)e.getKey()).equals("client_id") && !((String)e.getKey()).equals("code") && !((String)e.getKey()).equals("redirect_uri");
                }).collect(Collectors.toMap(Entry::getKey, (e) -> {
                    return (String)((List)e.getValue()).get(0);
                }));
                return new OAuth2AuthorizationCodeAuthenticationToken(code, clientPrincipal, redirectUri, additionalParameters);
            }
        }
    }
}
